/******************************************************************************
 * 
 * Announce: CSharpKit, Basic algorithms, components and definitions.
 *           Copyright (C) ShenYongchen.
 *           All rights reserved.
 *   Author: 申永辰.郑州 (shenyczz@163.com)
 *  WebSite: http://github.com/shenyczz/CSharpKit
 *
 * THIS CODE IS LICENSED UNDER THE MIT LICENSE (MIT).
 * THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
 * IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
 * PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
 * 
******************************************************************************/

using System;

namespace CSharpKit.SVM
{
    /// <summary>
    /// 包含训练SVM模型的例程。
    /// 训练和交叉验证。
    /// </summary>
    public static class Training
    {
        /// <summary>
        /// Whether the system will output information to the console during the training process.
        /// </summary>
        public static bool IsVerbose
        {
            get { return Procedures.IsVerbose; }
            set { Procedures.IsVerbose = value; }
        }




        public static void SetRandomSeed(int seed)
        {
            Procedures.setRandomSeed(seed);
        }


        /// <summary>
        /// 执行交叉验证
        /// </summary>
        /// <param name="problem">The training data</param>
        /// <param name="parameters">The parameters to test</param>
        /// <param name="nrfold">The number of cross validations to use（作用？）</param>
        /// <returns>The cross validation score (得分)</returns>
        public static double PerformCrossValidation(SvmProblem problem, SvmParameter parameters, int nrfold)
        {
            string error = Procedures.svm_check_parameter(problem, parameters);
            if (error == null)
                return doCrossValidation(problem, parameters, nrfold);
            else throw new Exception(error);
        }
        private static double doCrossValidation(SvmProblem problem, SvmParameter parameters, int nr_fold)
        {
            int i;
            double[] target = new double[problem.Count];
            Procedures.svm_cross_validation(problem, parameters, nr_fold, target);
            int total_correct = 0;
            double total_error = 0;
            double sumv = 0, sumy = 0, sumvv = 0, sumyy = 0, sumvy = 0;

            if (parameters.SvmType == SvmType.EPS_SVR || parameters.SvmType == SvmType.NU_SVR)
            {// 回归
                for (i = 0; i < problem.Count; i++)
                {
                    double y = problem.Y[i];
                    double v = target[i];
                    total_error += (v - y) * (v - y);
                    sumv += v;
                    sumy += y;
                    sumvv += v * v;
                    sumyy += y * y;
                    sumvy += v * y;
                }

                return (problem.Count * sumvy - sumv * sumy)
                     / (System.Math.Sqrt(problem.Count * sumvv - sumv * sumv) * System.Math.Sqrt(problem.Count * sumyy - sumy * sumy));
            }
            else
            {// 分类
                for (i = 0; i < problem.Count; i++)
                {
                    if (target[i] == problem.Y[i])
                        ++total_correct;
                }
            }

            return (double)total_correct / problem.Count;
        }


        /// <summary>
        /// 使用所提供的训练数据和参数训练模型。<br/>
        /// Trains a model using the provided training data and parameters.
        /// </summary>
        /// <param name="problem">The training data</param>
        /// <param name="parameters">The parameters to use</param>
        /// <returns>A trained SVM Model</returns>
        public static SvmModel Train(SvmProblem problem, SvmParameter parameters)
        {
            string error = Procedures.svm_check_parameter(problem, parameters);

            if (error == null)
                return Procedures.svm_train(problem, parameters);
            else
                throw new Exception(error);
        }





        /// <summary>
        /// Legacy.  Allows use as if this was svm_train.<br/>
        /// See libsvm documentation for details on which arguments to pass.
        /// </summary>
        /// <param name="args">命令行参数</param>
        [Obsolete("Provided only for legacy compatibility, use the other Train() methods")]
        public static void Train(params string[] args)
        {
            SvmParameter parameters;
            SvmProblem problem;
            bool crossValidation;
            int nrfold;
            string modelFilename;
            parseCommandLine(args, out parameters, out problem, out crossValidation, out nrfold, out modelFilename);
            if (crossValidation)
                PerformCrossValidation(problem, parameters, nrfold);
            else SvmModel.Write(modelFilename, Train(problem, parameters));
        }

        private static void parseCommandLine(string[] args, out SvmParameter parameters, out SvmProblem problem, out bool crossValidation, out int nrfold, out string modelFilename)
        {
            int i;

            parameters = new SvmParameter();
            // default values

            crossValidation = false;
            nrfold = 0;

            // parse options
            for (i = 0; i < args.Length; i++)
            {
                if (args[i][0] != '-')
                    break;
                ++i;
                switch (args[i - 1][1])
                {

                    case 's':
                        parameters.SvmType = (SvmType)int.Parse(args[i]);
                        break;

                    case 't':
                        parameters.KernelType = (KernelType)int.Parse(args[i]);
                        break;

                    case 'd':
                        parameters.Degree = int.Parse(args[i]);
                        break;

                    case 'g':
                        parameters.Gamma = double.Parse(args[i]);
                        break;

                    case 'r':
                        parameters.Coef0 = double.Parse(args[i]);
                        break;

                    case 'n':
                        parameters.Nu = double.Parse(args[i]);
                        break;

                    case 'm':
                        parameters.CacheSize = double.Parse(args[i]);
                        break;

                    case 'c':
                        parameters.C = double.Parse(args[i]);
                        break;

                    case 'e':
                        parameters.Eps = double.Parse(args[i]);
                        break;

                    case 'p':
                        parameters.P = double.Parse(args[i]);
                        break;

                    case 'h':
                        parameters.Shrinking = int.Parse(args[i]) == 1;
                        break;

                    case 'b':
                        parameters.Probability = int.Parse(args[i]) == 1;
                        break;

                    case 'v':
                        crossValidation = true;
                        nrfold = int.Parse(args[i]);
                        if (nrfold < 2)
                        {
                            throw new ArgumentException("n-fold cross validation: n must >= 2");
                        }
                        break;

                    case 'w':
                        parameters.Weights[int.Parse(args[i - 1].Substring(2))] = double.Parse(args[1]);
                        break;

                    default:
                        throw new ArgumentException("Unknown Parameter");
                }
            }

            // determine filenames

            if (i >= args.Length)
                throw new ArgumentException("No input file specified");

            problem = SvmProblem.Read(args[i]);

            if (parameters.Gamma == 0)
                parameters.Gamma = 1.0 / problem.MaxIndex;

            if (i < args.Length - 1)
                modelFilename = args[i + 1];
            else
            {
                int p = args[i].LastIndexOf('/') + 1;
                modelFilename = args[i].Substring(p) + ".model";
            }
        }


        //}}@@@
    }

}
